﻿using System;
using System.Collections.Generic;
using System.Configuration.Provider;
using System.IO;
using System.Linq;
using DatabaseStorageDemo.Model;
using TallComponents.Web.Pdf;
using TallComponents.Web.Storage;

namespace DatabaseStorageDemo.Storage
{
   /// <summary>
   /// Storage provider stores files in a database.
   /// </summary>
   public class DatabaseStorageProvider : PdfStorageProvider
   {
      /// <summary>
      /// Initializes the provider.
      /// </summary>
      /// <param name="name">The friendly name of the provider.</param>
      /// <param name="config">A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.</param>
      /// <exception cref="T:System.ArgumentNullException">
      /// The name of the provider is null.
      /// </exception>
      /// <exception cref="T:System.ArgumentException">
      /// The name of the provider has a length of zero.
      /// </exception>
      /// <exception cref="T:System.InvalidOperationException">
      /// An attempt is made to call <see cref="M:System.Configuration.Provider.ProviderBase.Initialize(System.String,System.Collections.Specialized.NameValueCollection)"/> on a provider after the provider has already been initialized.
      /// </exception>
      public override void Initialize( string name, System.Collections.Specialized.NameValueCollection config )
      {
         base.Initialize( name, config );

         if ( string.IsNullOrEmpty( config[ "connectionString" ] ) )
            throw new ProviderException( "Missing required attribute 'connectionString' on DatabaseStorageProvider." );

         _connectionString = System.Configuration.ConfigurationManager.ConnectionStrings[
            config[ "connectionString" ] ].ConnectionString;

         using ( var context = new FileStorageDataContext( _connectionString ) )
         {
            if ( !context.DatabaseExists() )
            {
               try
               {
                  context.CreateDatabase();
               }
               catch ( Exception ex )
               {
                  throw new ProviderException(
                     "Unable to create database. Please review the connection string and make sure it's valid for your system.\n" +
                     "The default configuration will try to connect to SqlServer Express and create the demonstration database.",
                     ex );
               }
            }
         }
      }

      /// <summary>
      /// Gets information about a PDF document.
      /// </summary>
      /// <param name="key">The storage key.</param>
      /// <returns>
      /// A <see cref="Document"/> object describing the document with the specified <paramref name="key"/> 
      /// or <c>null</c> if the key is not valid or associated with an invalid document.
      /// </returns>
      public override Document GetDocument( string key )
      {
         Document result = null;
         int id;
         if ( Int32.TryParse( key, out id ) )
         {
            using ( var context = new FileStorageDataContext( _connectionString ) )
            {
               var query =
                  from file in context.Files
                  where file.FileId == id
                  select new Document( new MemoryStream( file.Data ), file.FileName, file.FileId.ToString(), null, file.LastModified );

               result = query.FirstOrDefault();
            }
         }

         return result;
      }

      /// <summary>
      /// Stores a file.
      /// </summary>
      /// <param name="fileName">Name of the file.</param>
      /// <param name="data">A seekable stream containing the file data.</param>
      /// <returns>The unique key for the file.</returns>
      public override string StoreFile( string fileName, Stream data )
      {
         if ( null == data || data.Length == 0 )
         {
            throw new ArgumentException( "Stream contains no data", "data" );
         }

         using ( var insertContext = new FileStorageDataContext( _connectionString ) )
         {
            var newFile = new DatabaseFile
            {
               FileName = fileName,
               Data = new byte[ data.Length ],
               LastModified = DateTime.Now
            };

            if ( data.CanSeek )
            {
               data.Seek( 0, SeekOrigin.Begin );
            }
            data.Read( newFile.Data, 0, ( int )data.Length );

            try
            {
               var document = new Document(new MemoryStream(newFile.Data), fileName, string.Empty /* don't know the key yet */, null, newFile.LastModified);
               newFile.Author = document.DocumentInfo.Author;
               newFile.Creator = document.DocumentInfo.Creator;
               newFile.Keywords = document.DocumentInfo.Keywords;
               newFile.PageCount = document.Pages.Count;
               newFile.Subject = document.DocumentInfo.Subject;
               newFile.Title = document.DocumentInfo.Title;
            }
            catch ( Exception ex )
            {
               throw new InvalidOperationException( "The uploaded document does not appear to be valid", ex );
            }

            insertContext.Files.InsertOnSubmit( newFile );

            insertContext.SubmitChanges();

            return newFile.FileId.ToString();
         }
      }

      /// <summary>
      /// Enumerates the documents in this storage provider.
      /// </summary>
      /// <returns>
      /// Document information for each document in storage.
      /// </returns>
      public IEnumerable<DatabaseDocumentInfo> ListDocuments()
      {
         var datacontext = new FileStorageDataContext( _connectionString );
         var query = from file in datacontext.GetTable<DatabaseFile>()
                     orderby file.FileName
                     select
                        new DatabaseDocumentInfo( file.Title ?? file.FileName, file.Subject, file.Keywords, file.Author,
                                          file.Creator, file.FileId, file.PageCount, file.FileName, file.LastModified );

         return query;
      }

      /// <summary>
      /// Gets information on all the documents in storage.
      /// </summary>
      /// <returns>
      /// A list of <see cref="Document"/> objects, describing all PDF documents managed by this provider.
      /// </returns>
      public override IList<Document> GetDocuments()
      {
         var datacontext = new FileStorageDataContext( _connectionString );
         var query = from file in datacontext.GetTable<DatabaseFile>()
                     orderby file.FileName
                     select new Document( new MemoryStream( file.Data ),
                                          file.FileName,
                                          file.FileId.ToString(), null, file.LastModified );

         return query.ToList();
      }

      private string _connectionString;
   }
}
